#include "wx/wxprec.h"
#include "wx/app.h"
#include "wx/thread.h"
#include "wx/gtk/win_gtk.h"
#include "wx/gtk/private.h"
#include <gdk/gdkx.h>

#if wxUSE_MIMETYPE && wxUSE_LIBGNOMEVFS
#include "wx/link.h"
wxFORCE_LINK_MODULE( gnome_vfs )
#endif

bool   g_mainThreadLocked = false;
static GtkWidget *gs_RootWindow = ( GtkWidget* ) NULL;
void wxapp_install_idle_handler();
static wxMutex gs_idleTagsMutex;

bool wxIsInsideYield = false;

bool wxApp::Yield( bool onlyIfNeeded ) {
  if( wxIsInsideYield ) {
    if( !onlyIfNeeded ) {
      wxFAIL_MSG( wxT( "wxYield called recursively" ) );
    }
    return false;
  }
  #if wxUSE_THREADS
  if( !wxThread::IsMain() ) {
    return true;
  }
  #endif // wxUSE_THREADS
  wxIsInsideYield = true;
  SuspendIdleCallback();
  #if wxUSE_LOG
  wxLog::Suspend();
  #endif
  while( gtk_events_pending() ) {
    gtk_main_iteration();
  }
  ProcessIdle();
  wxLog::Resume();
  wxIsInsideYield = false;
  return true;
}

void wxApp::WakeUpIdle() {
  wxapp_install_idle_handler();
}

extern "C"{
  static gboolean
  event_emission_hook( GSignalInvocationHint*, guint, const GValue*, gpointer data ) {
    wxapp_install_idle_handler();
    bool* hook_installed = ( bool* )data;
    *hook_installed = false;
    return false;
  }

  static inline void wxAddEmissionHook() {
    static bool hook_installed;
    GType widgetType = GTK_TYPE_WIDGET;
    if( !hook_installed && g_type_class_peek( widgetType ) ) {
      static guint sig_id;
      if( sig_id == 0 ) {
        sig_id = g_signal_lookup( "event", widgetType );
      }
      hook_installed = true;
      g_signal_add_emission_hook(
        sig_id, 0, event_emission_hook, &hook_installed, NULL );
    }
  }

  static gint wxapp_idle_callback( gpointer( data ) ) {
    if( !wxTheApp ) {
      return false;
    }
    guint idleID_save;{
      #if wxUSE_THREADS
      wxMutexLocker lock( gs_idleTagsMutex );
      #endif
      idleID_save = wxTheApp->m_idleTag;
      wxTheApp->m_idleTag = 0;
      g_isIdle = true;
      wxAddEmissionHook();
    }
    #ifdef __WXDEBUG__
    if( wxTheApp->IsInAssert() ) {
      return false;
    }
    #endif // __WXDEBUG__
    gdk_threads_enter();
    bool moreIdles;
    do {
      moreIdles = wxTheApp->ProcessIdle();
    } while( moreIdles && gtk_events_pending() == 0 );
    // Release lock again
    gdk_threads_leave();
    #if wxUSE_THREADS
    wxMutexLocker lock( gs_idleTagsMutex );
    #endif
    // If another idle source was added, remove it
    if( wxTheApp->m_idleTag != 0 ) {
      g_source_remove( wxTheApp->m_idleTag );
    }
    wxTheApp->m_idleTag = idleID_save;
    g_isIdle = false;
    #if wxUSE_THREADS
    if( wxPendingEventsLocker ) {
      wxPendingEventsLocker->Enter();
    }
    #endif
    moreIdles = moreIdles || ( wxPendingEvents && !wxPendingEvents->IsEmpty() );
    #if wxUSE_THREADS
    if( wxPendingEventsLocker ) {
      wxPendingEventsLocker->Leave();
    }
    #endif
    if( !moreIdles ) {
      g_isIdle = true;
      wxTheApp->m_idleTag = 0;
      wxAddEmissionHook();
    }
    return moreIdles;
  }
}

static GPollFunc wxgs_poll_func;

extern "C" {
  static gint wxapp_poll_func( GPollFD *ufds, guint nfds, gint timeout ) {
    gdk_threads_enter();
    wxMutexGuiLeave();
    g_mainThreadLocked = true;
    gint res = ( *wxgs_poll_func )( ufds, nfds, timeout );
    wxMutexGuiEnter();
    g_mainThreadLocked = false;
    gdk_threads_leave();
    return res;
  }
}

void wxapp_install_idle_handler() {
  if( wxTheApp == NULL ) {
    return;
  }
  wxMutexLocker lock( gs_idleTagsMutex );
  if( !g_isIdle ) {
    return;
  }
  wxASSERT_MSG( wxTheApp->m_idleTag == 0, wxT( "attempt to install idle handler twice" ) );
  g_isIdle = false;
  wxTheApp->m_idleTag = g_idle_add_full( G_PRIORITY_LOW, wxapp_idle_callback, NULL, NULL );
}

GtkWidget* wxGetRootWindow() {
  if( gs_RootWindow == NULL ) {
    gs_RootWindow = gtk_window_new( GTK_WINDOW_TOPLEVEL );
    gtk_widget_realize( gs_RootWindow );
  }
  return gs_RootWindow;
}

IMPLEMENT_DYNAMIC_CLASS( wxApp, wxEvtHandler )

BEGIN_EVENT_TABLE( wxApp, wxEvtHandler )
  EVT_IDLE( wxAppBase::OnIdle )
END_EVENT_TABLE()

wxApp::wxApp() {
  #ifdef __WXDEBUG__
  m_isInAssert = false;
  #endif // __WXDEBUG__
  m_idleTag = 0;
  g_isIdle = true;
  m_glVisualInfo = ( void * ) NULL;
  m_glFBCInfo = ( void * ) NULL;
}

wxApp::~wxApp() {
  if( m_idleTag ) {
    g_source_remove( m_idleTag );
  }
}

bool wxApp::OnInitGui() {
  if( !wxAppBase::OnInitGui() ) {
    return false;
  }
  if( m_glVisualInfo != NULL ) {
    GdkVisual* vis = gtk_widget_get_default_visual();
    GdkColormap *colormap = gdk_colormap_new( vis, FALSE );
    gtk_widget_set_default_colormap( colormap );
  } else {
    if( m_useBestVisual ) {
      if( m_forceTrueColour ) {
        GdkVisual* visual = gdk_visual_get_best_with_both( 24, GDK_VISUAL_TRUE_COLOR );
        if( !visual ) {
          wxLogError( wxT( "无法初始化视觉." ) );
          return false;
        }
        GdkColormap *colormap = gdk_colormap_new( visual, FALSE );
        gtk_widget_set_default_colormap( colormap );
      } else {
        if( gdk_visual_get_best() != gdk_visual_get_system() ) {
          GdkVisual* visual = gdk_visual_get_best();
          GdkColormap *colormap = gdk_colormap_new( visual, FALSE );
          gtk_widget_set_default_colormap( colormap );
        }
      }
    }
  }
  return true;
}

GdkVisual *wxApp::GetGdkVisual() {
  GdkVisual *visual = NULL;
  if( m_glVisualInfo ) {
    visual = gdkx_visual_get( ( ( XVisualInfo * ) m_glVisualInfo )->visualid );
  } else{ visual = gdk_drawable_get_visual( wxGetRootWindow()->window ); }
  wxASSERT( visual );
  return visual;
}

bool wxApp::Initialize( int& argc, wxChar **argv ) {
  bool init_result;
  if( !g_thread_supported() ) {
    g_thread_init( NULL );
  }
  wxgs_poll_func = g_main_context_get_poll_func( NULL );
  g_main_context_set_poll_func( NULL, wxapp_poll_func );
  gtk_set_locale();
  wxConvCurrent = &wxConvUTF8;
  wxString encName( wxGetenv( _T( "G_FILENAME_ENCODING" ) ) );
  encName = encName.BeforeFirst( _T( ',' ) );
  if( encName.CmpNoCase( _T( "@locale" ) ) == 0 ) {
    encName.clear();
  }
  encName.MakeUpper();
  if( encName.empty() ) {
    encName = wxLocale::GetSystemEncodingName().Upper();
    if( encName.empty() || encName == _T( "US-ASCII" ) ) {
      encName = _T( "UTF-8" );
    }
    wxSetEnv( _T( "G_FILENAME_ENCODING" ), encName );
  }
  #if wxUSE_WCHAR_T
  static wxConvBrokenFileNames fileconv( encName );
  wxConvFileName = &fileconv;
  #endif // wxUSE_WCHAR_T
  int i;
  char **argvGTK = new char *[argc + 1];
  for( i = 0; i < argc; i++ ) {
    argvGTK[i] = wxStrdupA( wxConvUTF8.cWX2MB( argv[i] ) );
  }
  argvGTK[argc] = NULL;
  int argcGTK = argc;
  init_result = gtk_init_check( &argcGTK, &argvGTK );
  if( argcGTK != argc ) {
    for( i = 0; i < argcGTK; i++ ) {
      while( strcmp( wxConvUTF8.cWX2MB( argv[i] ), argvGTK[i] ) != 0 ) {
        memmove( argv + i, argv + i + 1, ( argc - i )*sizeof( *argv ) );
      }
    }
    argc = argcGTK;
  }
  for( i = 0; i < argcGTK; i++ ) {
    free( argvGTK[i] );
  }
  delete [] argvGTK;
  if( !init_result ) {
    wxLogError( wxT( "无法初始化gtk，显示设置是否正确?" ) );
    return false;
  }
  this->argc = argc;
  this->argv = argv;
  gdk_threads_enter();
  if( !wxAppBase::Initialize( argc, argv ) ) {
    gdk_threads_leave();
    return false;
  }
  wxSetDetectableAutoRepeat( true );
  wxFont::SetDefaultEncoding( wxLocale::GetSystemEncoding() );
  WakeUpIdle();
  return true;
}

void wxApp::CleanUp() {
  gdk_threads_leave();
  wxAppBase::CleanUp();
}

#ifdef __WXDEBUG__

void wxApp::OnAssertFailure( const wxChar *file,int line,const wxChar* func,const wxChar* cond,const wxChar *msg ) {
  m_isInAssert = true;
  wxAppBase::OnAssertFailure( file, line, func, cond, msg );
  m_isInAssert = false;
}

#endif // __WXDEBUG__

void wxApp::SuspendIdleCallback() {
  #if wxUSE_THREADS
  wxMutexLocker lock( gs_idleTagsMutex );
  #endif
  if( m_idleTag != 0 ) {
    g_source_remove( m_idleTag );
    m_idleTag = 0;
    g_isIdle = true;
    wxAddEmissionHook();
  }
}
